﻿using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace Benchmarks
{
    public class MethodInvokeHelper {
        public MethodInvokeHelper( string field ) { Field = field; }
        public readonly String Field;
        public String Property { get { return Field; } }
        public String Method() { return Field; }
    }

    public class MethodInvocationSuite : BenchmarkSuite{
        public MethodInvocationSuite() :
            base( "Method Invocation Approaches", new DirectMethod(),
                  new DelegateMethod(),
                  new DynamicMethod() ) { }
    }

    public class DirectMethod : Benchmark
    {
        protected MethodInvokeHelper[] Items;
        public static MethodInvokeHelper Result;
        public static String Current;
        public override string Category { get { return "METHOD_INVOKE"; } }
        
        public override string Name { get { return "METHOD CALL"; } }
        public override void StartTrials() {
            Items = new MethodInvokeHelper[ Benchmarker.MAX ];
            for( int i=0 ; i < Benchmarker.MAX ; i++ ){ Items[i] = new MethodInvokeHelper( i.ToString() );}
        }
        
        public override void RunOverheadTest(int num_iterations)
        {
            for (int i = 0; i < Items.Length; i++)
            {
                Result = Items[i];
            }
        }

        public override void RunTrial(int num_iterations)
        {
            for (int i = 0; i < num_iterations; i++)
            {
                Current = Items[i].Method();
            }
        }
    }

    public class DelegateMethod : DirectMethod
    {
        public Func<MethodInvokeHelper, String> func;

        public override string Name
        {
            get { return "PROP VIA FUNC"; }
        }
        public override void StartTrials()
        {
            base.StartTrials();

            PropertyInfo prop = typeof(MethodInvokeHelper).GetProperty("Property");
            func = (Func<MethodInvokeHelper, String>)
                              Delegate.CreateDelegate(typeof(Func<MethodInvokeHelper, String>), null, prop.GetGetMethod());
        }

        public override void RunTrial(int num_iterations)
        {
            for (int i = 0; i < num_iterations; i++)
            {
                Current = func(Items[i]);
            }
        }
    }

    public class DynamicMethod : DirectMethod {
        private Func< object, object[], object > dynamic_func;
        public override string Name
        {
            get
            {
                return "DYNAMIC_METHOD";
            }
        }
        public override void StartTrials()
        {
            base.StartTrials();
            PropertyInfo prop = typeof(MethodInvokeHelper).GetProperty("Property");

            dynamic_func = Wrap( prop.GetGetMethod() );
        }
        public override void RunTrial(int num_iterations)
        {
            for( int i=0 ; i < num_iterations ; i++ ) {
                Current = (String) dynamic_func( Items[ i ], null );
            }
        }


        public static Func<object, object[], object> Wrap(MethodInfo method)
        {
            var dm = new System.Reflection.Emit.DynamicMethod(method.Name, typeof(object), new Type[] {
                                                                                      typeof( object ),
                                                                                      typeof( object[] )
                                                                                  }, method.DeclaringType, true);
            var il = dm.GetILGenerator();

            if (!method.IsStatic)
            {
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Unbox_Any, method.DeclaringType);
            }
            var parameters = method.GetParameters();
            for (int i = 0; i < parameters.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldelem_Ref);
                il.Emit(OpCodes.Unbox_Any, parameters[i].ParameterType);
            }
            il.EmitCall(method.IsStatic || method.DeclaringType.IsValueType
                             ? OpCodes.Call
                             : OpCodes.Callvirt, method, null);
            if (method.ReturnType == null || method.ReturnType == typeof(void))
            {
                il.Emit(OpCodes.Ldnull);
            }
            else if (method.ReturnType.IsValueType)
            {
                il.Emit(OpCodes.Box, method.ReturnType);
            }
            il.Emit(OpCodes.Ret);
            return (Func<object, object[], object>)dm.CreateDelegate(typeof(Func<object, object[], object>));
        }
    }

}
